# -*- coding: utf-8 -*-
# created on 2016/11/30

from sympy import oo, FiniteSet, EmptySet, Union, Interval, sympify, S, simplify

from mathsolver.functions.base import (BaseFunction, BaseFunc, BaseIneqs, BaseSymbolValue, BaseBelong, BaseValue,
                                       BasePieceFunc, BaseIneq, new_latex)
from mathsolver.functions.budengshi.common_opers import interval_2_ineq_list
from mathsolver.functions.budengshi.jiebudengshi import JieBuDengShi
from mathsolver.functions.budengshi.params_ineqs_funcs import BaseParamerInterval
from mathsolver.functions.daoshu.daoshuyunsuan import HanShuDaoShuYunSuan
from mathsolver.functions.hanshu.base import is_piecefunc, is_absfunc, is_constantfunc, get_cond
from mathsolver.functions.hanshu.dingyiyu import QiuDingYiYu
from mathsolver.functions.hanshu.helper import check_func, simplify_budengshi_item, intersection2, check_inter
from mathsolver.functions.hanshu.qujueduizhi import QuJueDuiZhi
from mathsolver.functions.hanshu.proof_hanshu_budengshi import ProofHanShuBuDengShi


class ChangShuDanDiao(BaseFunction):
    """常数的单调区间"""

    def solver(self, *args):
        func, cond = args
        f_name, f, symbol = func.name, func.expression, func.var
        self.steps.append(["", "函数 %s 在 %s 上为常数函数,没有单调性" % (new_latex(f), new_latex(cond))])
        self.label.add("常值函数的单调性")
        self.output.append(BaseValue([EmptySet(), EmptySet()]))
        return self


def dandiaoqujian_print(qujian):
    """打印单调区间：如果不等式解集为并集，用 , 分隔单个区间，不能写成并集的形式"""
    if isinstance(qujian, Union):
        qujian_print = ', '.join(new_latex(qujian_piece) for qujian_piece in qujian.args)
    else:
        qujian_print = new_latex(qujian)
    # 补充：三角函数区间输出可能含有
    if qujian._inf.has(sympify('k')):
        qujian_print += ", k ∈ Z"

    return qujian_print


class JueDuiZhiDanDiao(BaseFunction):
    """绝对值函数单调区间"""

    def solver(self, *args):
        func, cond = args
        f_name, f, symbol = func.name, func.expression, func.var

        # 绝对值化简为分段函数
        abs_solver = QuJueDuiZhi().solver(func)
        self.steps.extend(abs_solver.steps)

        # 求分段函数单调区间
        fenduan = BasePieceFunc({"var": symbol, "name": f_name, "type": "", "expression": abs_solver.output})
        fenduan_solver = FenDuanHanShuDanDiao().solver(fenduan, cond)

        self.steps.extend(fenduan_solver.steps)
        self.output = fenduan_solver.output
        self.label.add("绝对值函数的单调性或区间问题")
        return self


class FenDuanHanShuDanDiao(BaseFunction):
    """分段函数单调区间"""

    def solver(self, *args):
        func, cond = args
        f_name, f, symbol = func.name, func.expression, func.var

        # 求每一个分段上的单调区间
        up, down = EmptySet(), EmptySet()
        for f_exp, f_interval in f:
            u, d = DanDiaoQuJian().solver(
                BaseFunc({"var": symbol, "name": f_name, "type": "", "expression": f_exp}),
                intersection2(f_interval, cond)).output[0].value

            if isinstance(up, list):
                up.extend(u)
                down.extend(d)
            else:
                up, down = Union(u, up), Union(d, down)

        # 输出步骤
        if isinstance(up, list):
            # 含参数的情况
            fmt = ""
            res = []
            for belong, qujian in up:
                if qujian:
                    fmt += "当 %s 时，函数在 %s 上单调递增 "
                    res.extend([belong.printing(), dandiaoqujian_print(qujian)])
            self.steps.append(["", fmt % tuple(res)])

            fmt = ""
            res = []
            for belong, qujian in down:
                if qujian:
                    fmt += "当 %s 时，函数在 %s 上单调递减 "
                    res.extend([belong.printing(), dandiaoqujian_print(qujian)])
            self.steps.append(["", fmt % tuple(res)])
        elif not up and down:
            self.steps.append(['', "函数在 %s 上是减函数" % dandiaoqujian_print(down)])
        elif not down and up:
            self.steps.append(['', "函数在 %s 上是增函数" % dandiaoqujian_print(up)])
        elif up and down:
            self.steps.append(['', "函数在 %s 上是增函数,在 %s 上是减函数" %
                               (dandiaoqujian_print(up), dandiaoqujian_print(down))])

        self.label.add("分段函数的单调性或区间问题")
        self.output.append(BaseValue([up, down]))
        return self


def process_dandiao_qujian(interval, dingyiyu):
    """如果区间端点在函数定义域内部，将开区间修改成闭区间，
    Example 求 sqrt(x+1) 的单调区间，求导计算不等式后单调区间取不到 -1"""

    def satisfiy_mod_cond(val, is_open):
        return is_open and (val not in [oo, -oo]) and (val in dingyiyu)

    if interval == EmptySet() or interval == S.Reals:
        return interval
    elif isinstance(interval, Interval):
        l_val, r_val, l_open, r_open = interval.args
        if satisfiy_mod_cond(l_val, l_open):
            l_open = False
        if satisfiy_mod_cond(r_val, r_open):
            r_open = False
        return Interval(l_val, r_val, l_open, r_open)
    elif isinstance(interval, Union):
        return Union([process_dandiao_qujian(inter, dingyiyu) for inter in interval.args])


class DaoShuDanDiao(BaseFunction):
    """利用导数求函数的单调区间
    :return baseValue([up, down])
        Case 1. 不含参数：up, down 是 Interval/Union/EmptySet
        Case 2. 含参数： up, down 是 [[BaseBelong([a, "\\in", quzhi]), jieji], ...]
    """
    symbol1, symbol2 = [">=", "<="], ["增", "减"]
    flag = True

    def solver(self, *args):
        func, cond = args
        cond_list2 = interval_2_ineq_list(cond, func.var)
        # 求函数导数
        deriv = HanShuDaoShuYunSuan().solver(func).output[0]
        self.steps.append(["因为 {}".format(func.printing()), "求导得 {}".format(deriv.printing())])
        up_down = []
        for s1, s2 in zip(self.symbol1, self.symbol2):
            # 令导数大于0，或小于0
            new_expr = simplify_budengshi_item(deriv.expression)
            budengshi = [[str(new_expr), s1, '0']]
            # 求 deriv 定义域
            deriv_dingyiyu = QiuDingYiYu().solver(deriv)
            # 如果定义域相等：不需要考虑 cond
            if deriv_dingyiyu == cond:
                solver = JieBuDengShi().solver(BaseIneqs(budengshi)).output[0]
            # 否则考虑 cond
            else:
                if isinstance(cond, Union):
                    solver = JieBuDengShi().solver(BaseIneqs(budengshi), {func.var: cond}).output[0]
                else:
                    budengshi.extend(cond_list2)
                    solver = JieBuDengShi().solver(BaseIneqs(budengshi)).output[0]
            # 如果不含参，输出为 BaseSymbolValue
            if isinstance(solver, BaseSymbolValue):
                qujian = list(solver.value.values())[0]
                # 补充：如果区间端点在函数定义域内部，将开区间修改成闭区间
                qujian = process_dandiao_qujian(qujian, cond)
                if isinstance(qujian, (Interval, Union)):
                    self.steps.append(["令 {} {} 0".format(new_latex(deriv.expression), s1),
                                       "得：{} \\in {}".format(new_latex(deriv.var), new_latex(qujian))])
                    # 输出优化：如果不等式解集为并集，用、分隔单个区间，不能写成并集的形式
                    self.steps.append(
                        ["", "结合函数的定义域，可知函数的单调递%s区间是 %s" % (s2, dandiaoqujian_print(qujian))])
                else:
                    qujian = EmptySet()
                up_down.append(qujian)
            # 如果含参数，输出为 baseParamerInterval
            # baseParamerInterval({'var':'x', 'paramer_var':'a', 'solveset':((a的取值范围1, x的解集1), (a的取值范围2, x的解集2))})
            elif isinstance(solver, BaseParamerInterval):
                # 搜索题目中参数的给定范围
                canshu_interval = self.search(sympify(solver.value['paramer_var']))
                if canshu_interval is None:
                    canshu_interval = S.Reals
                qujian = []
                a = solver.value['paramer_var']
                solver_result = solver.value['solveset']
                for quzhi, jieji in solver_result:
                    # 细节：如果 quzhi 不是区间，先检测函数是否为常数，转换成 FiniteSet
                    if not isinstance(quzhi, (Interval, Union)):
                        expr_test = simplify(func.expression.subs(a, quzhi))
                        # 细节：如果这个时候函数取值为常数，则不存在单调性
                        if not expr_test.has(func.var):
                            self.steps.append(
                                ["", "当 %s = %s 时, 函数不是单调函数" % (new_latex(a), new_latex(quzhi))])
                            continue
                        else:
                            quzhi = FiniteSet(quzhi)
                    # 细节：如果取值不在 canshu_interval里面，跳过
                    if not quzhi.intersect(canshu_interval):
                        continue
                    # 细节：如果 jieji 不是区间，不考虑（有可能出现这种输出：当 a∈2√2 时,函数的单调增区间为：2√2）
                    if not isinstance(jieji, (Interval, Union)):
                        continue
                    else:
                        # 补充：如果区间端点在函数定义域内部，将开区间修改成闭区间
                        jieji = process_dandiao_qujian(jieji, cond)
                    # 输出优化：如果不等式解集为并集，用、分隔单个区间，不能写成并集的形式
                    self.steps.append(["", "当 %s \\in %s 时, 函数的单调%s区间为：%s" % (
                    new_latex(a), new_latex(quzhi), s2, dandiaoqujian_print(jieji))])
                    qujian.append([BaseBelong([a, "\\in", quzhi]), jieji])
                up_down.append(qujian)
        self.output.append(BaseValue(up_down))
        self.label.add("利用导数求函数的单调区间")
        return self


class DanDiaoQuJian(BaseFunction):
    """求函数单调区间
    :return baseValue([up, down])
        Case 1. 不含参数：up, down 是 Interval/Union/EmptySet
        Case 2. 含参数： up, down 是 [[BaseBelong([a, "\\in", quzhi]), jieji], ...]
    """

    def solver(self, *args):
        func = check_func(args[0])
        func_name, expr, var = func.name, func.expression, func.var

        if isinstance(func, BasePieceFunc):
            cond = get_cond(args)
        else:
            # 补充：如果范围在 dingyiyu 里面给出
            if func.dingyiyu:
                dingyiyu = check_inter(func.dingyiyu)
            else:
                dingyiyu = QiuDingYiYu().solver(func)

            cond = intersection2(get_cond(args), dingyiyu)

            # 补充：如果题目给定范围
            known_var_range = self.search(var)
            if known_var_range:
                cond = intersection2(cond, known_var_range)

        if is_constantfunc(func):
            return ChangShuDanDiao().solver(func, cond)
        elif is_piecefunc(func):
            return FenDuanHanShuDanDiao().solver(func, cond)
        elif is_absfunc(func):
            return JueDuiZhiDanDiao().solver(func, cond)
        else:
            return DaoShuDanDiao(self.known).solver(func, cond)


class DiJianQuJian(BaseFunction):
    """求函数单调递减区间"""

    def solver(self, *args):
        dandiao_solver = DanDiaoQuJian(self.known).solver(*args)
        dandiao_solver.output = [BaseValue(dandiao_solver.output[0].value[1])]
        return dandiao_solver


class DiZengQuJian(BaseFunction):
    """求函数单调递增区间"""

    def solver(self, *args):
        dandiao_solver = DanDiaoQuJian(self.known).solver(*args)
        dandiao_solver.output = [BaseValue(dandiao_solver.output[0].value[0])]
        return dandiao_solver


class ProofDanDiaoXing2(BaseFunction):
    """证明函数在区间上的单调性 - 证明f' >= 0 不等式"""

    def solver(self, *args):
        func, interval, dandiaoxing = check_func(args[0]), check_inter(args[1]), args[2]

        # 求函数导数 f'(x)
        deriv = HanShuDaoShuYunSuan().solver(func).output[0]
        self.steps.append(["", "求导得 {}".format(deriv.printing())])

        # 证明 f'(x) >= 0 或者 <=0 在区间上恒成立
        op = '>=' if "增" in dandiaoxing else '<='
        self.steps.append(["", "问题等价于证明 f'(x) %s 在 %s 上恒成立" % (op, new_latex(interval))])
        try:
            pf_ineq = ProofHanShuBuDengShi(self.known).solver(BaseBelong([func.var, '\\in', interval]),
                                                              BaseIneq([deriv.expression, op, 0]))
            self.steps.extend(pf_ineq.steps)
        # 可能出现Mathematica 极限解不出的情况
        except Exception:
            # 求函数导数 f''(x)
            dderiv = HanShuDaoShuYunSuan().solver(deriv).output[0]
            self.steps.append(["", "求导得 f''(x) = {}".format(new_latex(dderiv.expression))])
            self.steps.append(["", "根据 f''(x) 的值可知 f'(x) %s 0 在 %s 上恒成立，得证" % (op, new_latex(interval))])

        self.label.add("证明函数在区间上的单调性")
        return self


class ProofDanDiaoXing1(BaseFunction):
    """证明函数在区间上的单调性 - 计算单调区间法"""

    def solver(self, *args):
        func, interval, dandiaoxing = check_func(args[0]), check_inter(args[1]), args[2]

        # 求出函数单调区间
        dandiao_solver = DanDiaoQuJian().solver(func)
        self.steps.extend(dandiao_solver.steps)

        # 判断给定区间 D 是所求单调区间的子集
        self.steps.append(["", "所以 %s 是函数单调递%s区间的子集，得证" % (new_latex(interval), dandiaoxing)])
        self.label.add("证明函数在区间上的单调性")
        return self


class ProofDanDiaoXing(BaseFunction):
    """证明函数在区间上的单调性"""

    def solver(self, *args):
        clss = [ProofDanDiaoXing1, ProofDanDiaoXing2]
        for cls in clss:
            try:
                return cls().solver(*args)
            except Exception:
                continue

        raise RuntimeError("cannot solve problem")


if __name__ == '__main__':
    pass
